
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>

#include <assert.h>


/* --- Data Structures ---------------------------------------------------- */

struct address {
	char *name;
	char *address;
	char *phone; };

struct list_element {
	struct list_element *prev;
	struct list_element *next;
	struct address *addr; };

struct list {
	struct list_element *first;
	struct list_element *last; };


/* --- Helper Functions --------------------------------------------------- */

#define INPUT_BUF_SIZE 1024

char *input (int n)
{
	static char buf[INPUT_BUF_SIZE];
	char c;
	int  i;

	i = 0;
	while (c != '\n' && i <= n && i < INPUT_BUF_SIZE)
	{ c = getchar();
	printf ("%c", c);
	if (c != '\n' ) { buf[i] = c; i++; } }

	buf[i] = '\0';

	return (buf);
}


/* --- List Implementation ------------------------------------------------ */

struct list_element *list_new_elem (struct address *addr)
{
	struct list_element *elem;

	elem = malloc (sizeof (struct list_element));
	if (!elem) { return (NULL); }
	assert(elem);

	elem->prev = elem;
	elem->next = elem;
	elem->addr = addr;

	return (elem);
}

struct list *list_new (void)
{
	struct list *list = malloc (sizeof (struct list));;

	if (!list) { return (NULL); }
	assert(list);
	list->first = NULL;
	list->last  = NULL;

	return (list);
}

void list_append (struct list *list, struct address *addr)
{
	struct list_element *elem = NULL;

	if (!list) { return; }
	elem = list_new_elem(addr);
	if (!elem) { return; }
	assert(list);
	if (list->first == NULL && list->last == NULL)
	{
		assert(elem);
		list->first = elem;
		list->last  = elem;
		elem->prev  = elem;
		elem->next  = elem;
	}
	else
	{
		assert(elem);
		assert(list->last);
		assert(list->first);
		elem->prev = list->last;
		elem->next = list->first;
		list->last->next = elem;
		list->first->prev = elem;
		list->last = elem;
	}

	return;
}

void list_remove (struct list *list, struct list_element *elem)
{
	if (!elem || !list) { return; }
	assert(list);
	assert(elem);
	if (elem == list->first && elem == list->last)
	{
		list->first = NULL;
		list->last  = NULL;
	}
	else if (elem == list->first)
	{
		assert(list->first);
		list->first = elem->next;
		list->first->prev = list->last;
	}
	else if (elem == list->last)
	{
		assert(list->last);
		list->last = elem->prev;
		list->last->next = list->first;
	}
	else
	{
		assert(elem->prev);
		assert(elem->next);
		elem->prev->next = elem->next;
		elem->next->prev = elem->prev;
	}
	if(elem->addr){
		assert(elem->addr);
		free (elem->addr);}
	free (elem);

	return;
}

void list_release (struct list *list)
{
	struct list_element *elem;

	if (!list) { return; }
	assert(list);
	elem = list->first;
	while (elem)
	{
		list_remove (list, elem);
		elem = list->first;
	}
	free (list);

	return;
}


/* --- Address Book Functionality ----------------------------------------- */

#define check_abort          { printf ("abort (y/n)? "); \
		c = 0; while (c != 'y' && c != 'n') \
		{ c = getchar(); printf ("%c", c); } \
		if (c == 'y') { goto ABORT_INPUT; } }
#define get_string(tag, dst) { printf (tag); \
		tmp = input (INPUT_BUF_SIZE); \
		if (strlen(tmp) == 0) { check_abort } \
		strcpy (dst,tmp); }


struct address *input_addr ()
{
	struct address *address;
	char *tmp, *name = NULL, *addr = NULL, *phone = NULL;
	char c;

	get_string ("name: ", name);
	get_string ("address: ", addr);
	get_string ("phone: ", phone);

	address = malloc (sizeof(struct address));
	assert(address);
	address->name    = name;
	address->address = addr;
	address->phone   = phone;
	return (address);

	ABORT_INPUT:
	if (name)  {
		assert(name);
		free (name);
	}
	if (addr)  {
		assert(addr);
		free (addr);
	}
	if (phone) {
		assert(phone);
		free (phone);
	}
	return (NULL);
}
void print_addr (struct address *addr)
{
	if(!addr || !addr->address || !addr->phone || !addr->name) return;
	assert(addr); assert(addr->address), assert(addr->phone), assert(addr->name);
	printf (addr->name); printf("; ");
	printf ("%s; %s\n", addr->address, addr->phone);
	return;
}

void list_addrs (struct list *list)
{
	struct list_element *elem = NULL;
	unsigned int i;
	assert(list);
	elem = list->first;
	i = 0;
	while (elem)
	{
		printf ("%04u:\n  ", i);
		assert(elem);
		print_addr (elem->addr);

		if (elem == list->last)
		{ elem = NULL; }
		else
		{ elem = elem->next; }
		i++; }

	return;
}

void del_addr (struct list *list)
{
	struct list_element *elem = NULL;
	unsigned int i, n;
	char *tmp;

	printf ("delete #: ");
	tmp = input (INPUT_BUF_SIZE);
	assert(tmp);
	n = atoi (tmp);
	assert(list);
	elem = list->first;
	i = 0;
	while (elem && i != n)
	{

		if (elem == list->last)
		{ elem = NULL; }
		else
		{
			assert(elem);
			elem = elem->next;
		}
		i++;
	}
	list_remove (list, elem);

	return;
}

int cmp_addr (struct address *addr, char *str)
{
	assert(addr);
	assert(str);
	if(addr->name){
	assert(addr->name);
	if (strcasestr (addr->name, str))    { return (1); }}
	if(addr->address){
	assert(addr->address);
	if (strcasestr (addr->address, str)) { return (1); }}
	if(addr->phone){
	assert(addr->phone);
	if (strcasestr (addr->phone, str))   { return (1); }}

	return (0);
}

void search_addr (struct list *list)
{
	struct list_element *elem = NULL;
	unsigned int i;
	char *str;

	printf ("search: ");
	str = input (INPUT_BUF_SIZE);
	assert(list);
	elem = list->first;
	i = 0;
	while (elem)
	{
		assert(str);
		if(elem->addr){
			assert(elem->addr);
			if (cmp_addr (elem->addr, str))
			{ printf ("%04u:\n  ", i);
			print_addr (elem->addr); }
			if (elem == list->last)
			{ elem = NULL; }
			else
			{ elem = elem->next; }
			i++; }
	}
	return;
}


/* --- main() ------------------------------------------------------------- */

int main (int argc, char **argv)
{
	struct termios oldT, newT;
	struct list *list = NULL;
	char c = 0;


	/* terminal setup */
	ioctl(0,TCGETS,&oldT);
	newT=oldT;
	newT.c_lflag &= ~ECHO;
	newT.c_lflag &= ~ICANON;
	ioctl(0,TCSETS,&newT);


	/* list initialisation */
	list = list_new ();

	/* main loop */
	while (c != 'q')
	{
		printf ("a: add; l: list; d: delete; s: search; q: quit\n");
		c = getchar();
		if (c != '\n') { printf ("%c\n", c); }
		switch (c) {
		case 'a': { struct address *addr;
		addr = input_addr();
		list_append (list, addr);
		break; }
		case 'l': { list_addrs (list);
		break; }
		case 'd': { del_addr (list);
		break; }
		case 's': { search_addr (list);
		break; }
		default : { break; } }
	}

	/* cleanup */
	list_release (list);


	/* restore terminal settings */
	ioctl(0,TCSETS,&oldT);

	return (0);
}

